package entity.evaluation;

import entity.domain.Domain;
import entity.domain.DomainHelper;
import entity.domain.topic.*;
import entity.domain.value.ContinuousValue;
import entity.domain.value.DiscreteValue;
import entity.domain.value.Value;
import entity.offer.bid.Bid;
import entity.evaluation.topic.ContinuousTopicEvaluator;
import entity.evaluation.topic.DiscreteTopicEvaluator;
import entity.evaluation.topic.TopicEvaluator;
import entity.evaluation.value.ContinuousValueEvaluator;
import entity.evaluation.value.DiscreteValueEvaluator;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class EvaluatorHelper {

    private Evaluator evaluator;
    private Domain domain;

    private EvaluatorHelper(){}

    public EvaluatorHelper(Domain domain, Evaluator evaluator) {
        this.domain = domain;
        this.evaluator = evaluator;
    }

    public Evaluator getEvaluator() {
        return evaluator;
    }

    public void setEvaluator(Evaluator evaluator) {
        this.evaluator = evaluator;
    }

    public Domain getDomain() {
        return domain;
    }

    public void setDomain(Domain domain) {
        this.domain = domain;
    }

    /**
     * 创建EvaluatorHelper实例，
     * @param evaluationPath xml文件的路径：domain_xml/domainName/domain.xml
     * @param domain 如果为空，则自动获取Domain
     * @return
     */
    public static EvaluatorHelper createEvaluatorHelper(String evaluationPath, Domain domain){
        Evaluator evaluator = new Evaluator();
        SAXReader reader = new SAXReader();
        Document doc = null;
        try{
            doc = reader.read(new InputStreamReader(EvaluatorHelper.class.getResourceAsStream(evaluationPath)));
            String domainName = doc.getRootElement().attributeValue("domain");
            if(domain==null||!domainName.equals(domain.getName())){
                domain = DomainHelper.loadDomainFromXML("domain_xml/"+domainName+"/domain.xml");
            }
            evaluator.setDomainName(domainName);
            List<Node> topicNodes = doc.selectNodes("//topic");
            List<TopicEvaluator> topicEvaluators = new ArrayList<>();
            for(Node topicNode: topicNodes){
                Element topicEle = (Element)topicNode;
                String topicType = topicEle.attributeValue("type");
                TopicEvaluator topicEvaluator = null;
                if("discrete".equals(topicType)){
                    topicEvaluator = new DiscreteTopicEvaluator(Integer.parseInt(topicEle.attributeValue("num")), Double.parseDouble(topicEle.attributeValue("weight")));
                    List<DiscreteValueEvaluator> discreteValueEvaluators = new ArrayList<>();
                    List<Node> valueNodes = topicNode.selectNodes("//option");
                    double maxEvaluation = 0.0;
                    for(Node valueNode: valueNodes){
                        Element valueEle = (Element) valueNode;
                        double evaluation = Double.parseDouble(valueEle.getText());
                        DiscreteValueEvaluator discreteValueEvaluator = new DiscreteValueEvaluator(Integer.parseInt(valueEle.attributeValue("num")), evaluation);
                        discreteValueEvaluators.add(discreteValueEvaluator);
                        if(evaluation>maxEvaluation){
                            maxEvaluation = evaluation;
                        }
                    }
                    topicEvaluator.setValueEvaluator(discreteValueEvaluators);
                    topicEvaluator.setMaxEvalution(maxEvaluation);
                }else if("continuous".equals(topicType)){
                    topicEvaluator = new ContinuousTopicEvaluator(Integer.parseInt(topicEle.attributeValue("num")), Double.parseDouble(topicEle.attributeValue("weight")));
                    Element valueEle = (Element) topicNode.selectSingleNode("//formula");
                    double offset = Double.parseDouble(valueEle.attributeValue("offset"));
                    double slope = Double.parseDouble(valueEle.attributeValue("slope"));
                    ContinuousValueEvaluator continuousValueEvaluator = new ContinuousValueEvaluator(offset, slope);
                    topicEvaluator.setValueEvaluator(continuousValueEvaluator);
                    Topic topic = domain.getTopics().get(topicEvaluator.getTopicNum());
                    Range range = ((ContinuousTopic) topic).getRange();
                    topicEvaluator.setMaxEvalution(Math.max(offset,offset+slope*(range.getUpperbound() - range.getLowerbound())));
                }
                topicEvaluators.add(topicEvaluator);
            }
            evaluator.setTopicEvaluators(topicEvaluators);
        }catch (DocumentException e){
            e.printStackTrace();
        }
        return new EvaluatorHelper(domain, evaluator);
    }

    public double getEvaluation(Bid bid){
        double evaluation = 0.0;
        List<Value> values = bid.getValues();
        List<TopicEvaluator> topicEvaluators = evaluator.getTopicEvaluators();
        int amountOfValues = values.size();
        for(int i = 0; i<amountOfValues; i++){
            Value value = values.get(i);
            switch (value.getValueType()){
                case Discrete:
                    DiscreteValue discreteValue = (DiscreteValue) value;
                    DiscreteTopicEvaluator discreteTopicEvaluator = (DiscreteTopicEvaluator) evaluator.getTopicEvaluators().get(i);
                    DiscreteValueEvaluator discreteValueEvaluator = (DiscreteValueEvaluator) discreteTopicEvaluator.getValueEvaluator().get(discreteValue.getNum());
                    evaluation += discreteValueEvaluator.getEvaluation()*discreteTopicEvaluator.getWeight();
                    break;
                case Continuous:
                    ContinuousValue continuousValue = (ContinuousValue) value;
                    ContinuousTopicEvaluator continuousTopicEvaluator = (ContinuousTopicEvaluator) evaluator.getTopicEvaluators().get(i);
                    ContinuousValueEvaluator continuousValueEvaluator = (ContinuousValueEvaluator) continuousTopicEvaluator.getValueEvaluator();
                    Range range = ((ContinuousTopic)domain.getTopics().get(i)).getRange();
                    evaluation += (continuousValueEvaluator.getOffset()+continuousValueEvaluator.getSlope()*(((ContinuousValue) value).getValue()-range.getLowerbound()))*continuousTopicEvaluator.getWeight();
                    break;
            }
        }
        return evaluation;
     }

    public double getNormalizedEvaluation(Bid bid){
         double evaluation = 0.0;
         List<Value> values = bid.getValues();
         List<TopicEvaluator> topicEvaluators = evaluator.getTopicEvaluators();
         int amountOfValues = values.size();
         for(int i = 0; i<amountOfValues; i++){
             Value value = values.get(i);
             switch (value.getValueType()){
                 case Discrete:
                     DiscreteValue discreteValue = (DiscreteValue) value;
                     DiscreteTopicEvaluator discreteTopicEvaluator = (DiscreteTopicEvaluator) evaluator.getTopicEvaluators().get(i);
                     DiscreteValueEvaluator discreteValueEvaluator = (DiscreteValueEvaluator) discreteTopicEvaluator.getValueEvaluator().get(discreteValue.getNum());
                     double discreteEvaluation = discreteValueEvaluator.getEvaluation()*discreteTopicEvaluator.getWeight();
                     evaluation += discreteEvaluation/discreteTopicEvaluator.getMaxEvalution();
                     break;
                 case Continuous:
                     ContinuousValue continuousValue = (ContinuousValue) value;
                     ContinuousTopicEvaluator continuousTopicEvaluator = (ContinuousTopicEvaluator) evaluator.getTopicEvaluators().get(i);
                     ContinuousValueEvaluator continuousValueEvaluator = (ContinuousValueEvaluator) continuousTopicEvaluator.getValueEvaluator();
                     Range range = ((ContinuousTopic)this.domain.getTopics().get(i)).getRange();
                     evaluation += (continuousValueEvaluator.getOffset()+continuousValueEvaluator.getSlope()*(((ContinuousValue) value).getValue()-range.getLowerbound()))*continuousTopicEvaluator.getWeight()/continuousTopicEvaluator.getMaxEvalution();
                     break;
             }
         }
         return evaluation;
    }

    public Bid generateRandomBid(){
        Bid bid = new Bid();
        List<Value> values = new ArrayList<>();
        List<Topic> topics = domain.getTopics();
        Random rand = new Random();
        for(Topic topic: topics){
            switch (topic.getType()) {
                case Discrete:
                    int valueNum = rand.nextInt(((DiscreteTopic) topic).getOptions().size());
                    values.add((Value) ((DiscreteTopic) topic).getOptions().get(valueNum));
                    break;
                case Continuous:
                    Range range = ((ContinuousTopic) topic).getRange();
                    double figure = range.getLowerbound() + rand.nextDouble()*(range.getUpperbound() - range.getLowerbound());
                    values.add(new ContinuousValue(figure));
                    break;
            }
        }
        bid.setValues(values);
        return bid;
    }

    public static void main(String[] args) {
        EvaluatorHelper evaluatorHelper = createEvaluatorHelper("../domain/domain_xml/domain1/evaluation/evaluation1.xml", null);
        for(int i = 0; i < 10; i++){
            Bid bid = evaluatorHelper.generateRandomBid();
            System.out.println(bid);
            System.out.println(evaluatorHelper.getNormalizedEvaluation(bid));
        }
    }
}
